<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Edge-TTS 语音测试工具</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            padding: 20px;
            min-height: 100vh;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 2em;
            margin-bottom: 10px;
        }
        
        .header p {
            opacity: 0.9;
            font-size: 1.1em;
        }
        
        .main {
            padding: 30px;
        }
        
        .section {
            margin-bottom: 30px;
        }
        
        .section-title {
            font-size: 1.3em;
            color: #333;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 2px solid #667eea;
        }
        
        .input-group {
            margin-bottom: 20px;
        }
        
        .input-group label {
            display: block;
            margin-bottom: 8px;
            color: #555;
            font-weight: 500;
        }
        
        .input-group input,
        .input-group select,
        .input-group textarea {
            width: 100%;
            padding: 12px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 1em;
            transition: border-color 0.3s;
        }
        
        .input-group input:focus,
        .input-group select:focus,
        .input-group textarea:focus {
            outline: none;
            border-color: #667eea;
        }
        
        .input-group textarea {
            resize: vertical;
            min-height: 100px;
            font-family: inherit;
        }
        
        .slider-group {
            margin-bottom: 20px;
        }
        
        .slider-group label {
            display: block;
            margin-bottom: 8px;
            color: #555;
            font-weight: 500;
        }
        
        .slider-container {
            display: flex;
            align-items: center;
            gap: 15px;
        }
        
        .slider-container input[type="range"] {
            flex: 1;
        }
        
        .slider-value {
            min-width: 60px;
            text-align: center;
            padding: 8px;
            background: #f5f5f5;
            border-radius: 6px;
            font-weight: 600;
            color: #667eea;
        }
        
        .button-group {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
        }
        
        button {
            flex: 1;
            min-width: 150px;
            padding: 15px 30px;
            font-size: 1.1em;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            transition: all 0.3s;
            font-weight: 600;
        }
        
        button.primary {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        
        button.primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
        }
        
        button.secondary {
            background: #f5f5f5;
            color: #333;
        }
        
        button.secondary:hover {
            background: #e0e0e0;
        }
        
        button:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        
        .voice-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 15px;
            margin-top: 15px;
        }
        
        .voice-card {
            padding: 15px;
            border: 2px solid #e0e0e0;
            border-radius: 10px;
            cursor: pointer;
            transition: all 0.3s;
        }
        
        .voice-card:hover {
            border-color: #667eea;
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
        }
        
        .voice-card.active {
            border-color: #667eea;
            background: #f0f4ff;
        }
        
        .voice-name {
            font-weight: 600;
            color: #333;
            margin-bottom: 5px;
        }
        
        .voice-info {
            font-size: 0.9em;
            color: #666;
        }
        
        .status {
            margin-top: 20px;
            padding: 15px;
            border-radius: 10px;
            display: none;
        }
        
        .status.success {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        
        .status.error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        
        .status.info {
            background: #d1ecf1;
            color: #0c5460;
            border: 1px solid #bee5eb;
        }
        
        .loading {
            display: none;
            text-align: center;
            padding: 20px;
        }
        
        .loading.active {
            display: block;
        }
        
        .spinner {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #667eea;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
            margin: 0 auto 10px;
        }
        
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🎙️ Edge-TTS 语音测试工具</h1>
            <p>在线试听不同的中文语音效果</p>
        </div>
        
        <div class="main">
            <!-- 服务器配置 -->
            <div class="section">
                <div class="section-title">🔧 服务器配置</div>
                <div class="input-group">
                    <label>API 地址</label>
                    <input type="text" id="apiUrl" value="http://localhost:5000" placeholder="http://localhost:5000">
                </div>
                <div class="button-group">
                    <button class="secondary" onclick="checkServer()">🔍 检查服务器连接</button>
                </div>
            </div>
            
            <!-- 测试文本 -->
            <div class="section">
                <div class="section-title">📝 测试文本</div>
                <div class="input-group">
                    <textarea id="testText" placeholder="输入要转换的文本...">欢迎来到直播间！感谢大家的支持，点点关注不迷路。今天给大家带来超值优惠，错过今天等一年！</textarea>
                </div>
            </div>
            
            <!-- 语音选择 -->
            <div class="section">
                <div class="section-title">🎵 语音选择</div>
                <div class="button-group">
                    <button class="secondary" onclick="loadVoices()">刷新语音列表</button>
                </div>
                <div id="voiceGrid" class="voice-grid">
                    <!-- 语音卡片将在这里动态加载 -->
                </div>
            </div>
            
            <!-- 参数调整 -->
            <div class="section">
                <div class="section-title">⚙️ 参数调整</div>
                
                <div class="slider-group">
                    <label>语速</label>
                    <div class="slider-container">
                        <input type="range" id="rate" min="-50" max="100" value="0" step="10">
                        <span class="slider-value" id="rateValue">+0%</span>
                    </div>
                </div>
                
                <div class="slider-group">
                    <label>音量</label>
                    <div class="slider-container">
                        <input type="range" id="volume" min="0" max="100" value="50" step="5">
                        <span class="slider-value" id="volumeValue">+50%</span>
                    </div>
                </div>
                
                <div class="slider-group">
                    <label>音调</label>
                    <div class="slider-container">
                        <input type="range" id="pitch" min="-50" max="50" value="0" step="5">
                        <span class="slider-value" id="pitchValue">+0Hz</span>
                    </div>
                </div>
            </div>
            
            <!-- 操作按钮 -->
            <div class="section">
                <div class="button-group">
                    <button class="primary" onclick="testVoice()">🎧 试听语音</button>
                    <button class="secondary" onclick="downloadAudio()">💾 下载音频</button>
                    <button class="secondary" onclick="checkCache()">📊 查看缓存统计</button>
                </div>
            </div>
            
            <!-- 加载状态 -->
            <div class="loading" id="loading">
                <div class="spinner"></div>
                <p>正在生成语音...</p>
            </div>
            
            <!-- 状态提示 -->
            <div class="status" id="status"></div>
            
            <!-- 音频播放器 -->
            <audio id="audioPlayer" controls style="width: 100%; margin-top: 20px; display: none;"></audio>
        </div>
    </div>
    
    <script>
        let selectedVoice = 'zh-CN-XiaoxiaoNeural';
        let currentAudioUrl = null;
        
        // 初始化
        document.addEventListener('DOMContentLoaded', () => {
            checkServer();
            setupSliders();
        });
        
        // 检查服务器是否运行
        async function checkServer() {
            const apiUrl = document.getElementById('apiUrl').value;
            showStatus('正在检查服务器连接...', 'info');
            
            try {
                const response = await fetch(`${apiUrl}/health`);
                const data = await response.json();
                
                if (data.status === 'ok') {
                    showStatus('✅ 服务器连接成功！', 'success');
                    // 自动加载语音列表
                    setTimeout(() => loadVoices(), 500);
                } else {
                    showStatus('⚠️ 服务器响应异常', 'error');
                }
            } catch (error) {
                showStatus(`❌ 无法连接到服务器！请确保：
1. Edge-TTS 服务已启动（运行 start_edge_tts.bat）
2. 服务地址正确：${apiUrl}
3. 已安装 flask-cors（pip install flask-cors）

错误详情: ${error.message}`, 'error');
                
                // 显示帮助按钮
                const status = document.getElementById('status');
                const helpBtn = document.createElement('button');
                helpBtn.textContent = '📖 查看启动教程';
                helpBtn.className = 'secondary';
                helpBtn.style.marginTop = '10px';
                helpBtn.onclick = () => {
                    alert(`启动 Edge-TTS 服务步骤：

1. 打开命令行，进入项目目录
2. 运行: scripts\\start_edge_tts.bat
3. 看到 "服务地址: http://localhost:5000" 即表示启动成功
4. 刷新本测试页面

如果遇到错误：
- 确保已安装依赖: pip install edge-tts flask flask-cors
- 检查端口 5000 是否被占用
- 查看终端错误信息`);
                };
                status.appendChild(document.createElement('br'));
                status.appendChild(helpBtn);
            }
        }
        
        // 设置滑块
        function setupSliders() {
            const rate = document.getElementById('rate');
            const volume = document.getElementById('volume');
            const pitch = document.getElementById('pitch');
            
            rate.addEventListener('input', (e) => {
                const val = parseInt(e.target.value);
                document.getElementById('rateValue').textContent = (val >= 0 ? '+' : '') + val + '%';
            });
            
            volume.addEventListener('input', (e) => {
                const val = parseInt(e.target.value);
                document.getElementById('volumeValue').textContent = '+' + val + '%';
            });
            
            pitch.addEventListener('input', (e) => {
                const val = parseInt(e.target.value);
                document.getElementById('pitchValue').textContent = (val >= 0 ? '+' : '') + val + 'Hz';
            });
        }
        
        // 加载语音列表
        async function loadVoices() {
            const apiUrl = document.getElementById('apiUrl').value;
            showStatus('正在加载语音列表...', 'info');
            
            try {
                const response = await fetch(`${apiUrl}/voices/all`);
                
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                
                const data = await response.json();
                
                const voiceGrid = document.getElementById('voiceGrid');
                voiceGrid.innerHTML = '';
                
                // 按地区显示
                for (const [locale, region] of Object.entries(data.grouped)) {
                    if (region.voices.length > 0) {
                        region.voices.forEach(voice => {
                            const card = document.createElement('div');
                            card.className = 'voice-card';
                            if (voice.name === selectedVoice) {
                                card.classList.add('active');
                            }
                            
                            card.innerHTML = `
                                <div class="voice-name">${voice.gender} ${voice.name.split('-').pop()}</div>
                                <div class="voice-info">${voice.locale_name}</div>
                            `;
                            
                            card.onclick = () => {
                                document.querySelectorAll('.voice-card').forEach(c => c.classList.remove('active'));
                                card.classList.add('active');
                                selectedVoice = voice.name;
                                showStatus(`已选择: ${voice.name}`, 'success');
                            };
                            
                            voiceGrid.appendChild(card);
                        });
                    }
                }
                
                showStatus(`✅ 成功加载 ${data.total} 个语音`, 'success');
            } catch (error) {
                showStatus(`❌ 加载语音列表失败: ${error.message}
                
请确保 Edge-TTS 服务正在运行！`, 'error');
                console.error('详细错误:', error);
            }
        }
        
        // 测试语音
        async function testVoice() {
            const apiUrl = document.getElementById('apiUrl').value;
            const text = document.getElementById('testText').value;
            
            if (!text.trim()) {
                showStatus('请输入测试文本', 'error');
                return;
            }
            
            const rate = document.getElementById('rate').value;
            const volume = document.getElementById('volume').value;
            const pitch = document.getElementById('pitch').value;
            
            showLoading(true);
            showStatus('正在生成语音...', 'info');
            
            try {
                const response = await fetch(`${apiUrl}/tts`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        text: text,
                        voice: selectedVoice,
                        rate: (rate >= 0 ? '+' : '') + rate + '%',
                        volume: '+' + volume + '%',
                        pitch: (pitch >= 0 ? '+' : '') + pitch + 'Hz'
                    })
                });
                
                if (!response.ok) {
                    throw new Error('请求失败: ' + response.statusText);
                }
                
                const blob = await response.blob();
                
                // 释放旧的 URL
                if (currentAudioUrl) {
                    URL.revokeObjectURL(currentAudioUrl);
                }
                
                currentAudioUrl = URL.createObjectURL(blob);
                const audioPlayer = document.getElementById('audioPlayer');
                audioPlayer.src = currentAudioUrl;
                audioPlayer.style.display = 'block';
                audioPlayer.play();
                
                showStatus('语音生成成功！正在播放...', 'success');
            } catch (error) {
                showStatus('生成失败: ' + error.message, 'error');
            } finally {
                showLoading(false);
            }
        }
        
        // 下载音频
        async function downloadAudio() {
            if (!currentAudioUrl) {
                showStatus('请先生成语音', 'error');
                return;
            }
            
            const a = document.createElement('a');
            a.href = currentAudioUrl;
            a.download = `tts_${selectedVoice}_${Date.now()}.mp3`;
            a.click();
            
            showStatus('下载已开始', 'success');
        }
        
        // 查看缓存统计
        async function checkCache() {
            const apiUrl = document.getElementById('apiUrl').value;
            
            try {
                const response = await fetch(`${apiUrl}/cache/stats`);
                const data = await response.json();
                
                const msg = `
                    缓存统计：
                    总请求：${data.statistics.total_requests} 次
                    缓存命中：${data.statistics.cache_hits} 次
                    命中率：${data.statistics.hit_rate}
                    缓存文件：${data.cache.file_count} 个
                    占用空间：${data.cache.total_size_mb} MB
                `;
                
                alert(msg);
            } catch (error) {
                showStatus('查询失败: ' + error.message, 'error');
            }
        }
        
        // 显示状态
        function showStatus(message, type) {
            const status = document.getElementById('status');
            status.textContent = message;
            status.className = 'status ' + type;
            status.style.display = 'block';
            
            if (type === 'success') {
                setTimeout(() => {
                    status.style.display = 'none';
                }, 3000);
            }
        }
        
        // 显示加载状态
        function showLoading(show) {
            const loading = document.getElementById('loading');
            if (show) {
                loading.classList.add('active');
            } else {
                loading.classList.remove('active');
            }
        }
    </script>
</body>
</html>

